package com.dhy.cp

import java.io.File
import java.util.*

object ComponentProcessorUtil {
    private const val lineFormatter = "\t\tString %s = \"%s\";"
    private const val groupFormatter = "\tinterface %s {\n%s\n\t}"
    private val stringDefFormatter = """
        package %s;
        
        import androidx.annotation.StringDef;
        
        @StringDef({
        %s
        })
        public @interface %s{}
    """.trimIndent()
    private val componentsFormatter = """
        package %s;
        
        public interface %s {
        %s
        }
        %s
    """.trimIndent()
    private val String.classNameToComponentName: String
        get() {
            return substring(lastIndexOf('.') + 1).toUnderlineCase().uppercase(Locale.ROOT)
        }

    private fun gen(moduleName: String, classeNames: Set<String>): String {
        val lines = classeNames.sorted().map {
            lineFormatter.format(it.classNameToComponentName, it)
        }
        return groupFormatter.format(moduleName, lines.joinToString("\n"))
    }

    fun update(packageName: String, componentsFile: File, componentFile: File, moduleName: String, classeNames: List<String>) {
        val components = componentsFile.readModuleComponents()
        val head = "$moduleName:"
        val olds = components.filter { it.startsWith(head) }.map { it.substring(head.length) }.toSet()
        val news = classeNames.toSet()
        if (news != olds) {
            val modules = components.filter { !it.startsWith(head) }.map { ModuleComponent(it) }.groupBy { it.moduleName }.toMutableMap()
            modules[moduleName] = news.map { ModuleComponent(moduleName, it) }

            val body = modules.keys.sorted().joinToString("\n") { name ->
                val classes = modules.getValue(name).map { mc -> mc.className }.toSet()
                gen(name, classes)
            }

            val componentsClassName = componentsFile.name.replace(".java", "")
            val moduleComponents = modules.values.flatten()
            val comment = moduleComponents.map { it.toString() }.sorted().joinToString("\n")

            val componentsClass = componentsFormatter.format(packageName, componentsClassName, body, comment)
            componentsFile.writeBytes(componentsClass.toByteArray())

            updateComponentAnnotation(componentFile, packageName, componentsFile.toClassName, moduleComponents)
        }
    }

    private val File.toClassName: String
        get() {
            return name.replace(".java", "")
        }

    private fun File.readModuleComponents(): Set<String> {
        if (!exists()) return emptySet()
        return readText().split("\n")
            .filter { it.startsWith("//") }
            .map { it.substring(2) }
            .toSet()
    }

    private fun updateComponentAnnotation(componentFile: File, packageName: String, componentsClassName: String, moduleComponents: List<ModuleComponent>) {
        val componentClassName = componentFile.toClassName
        val stringDefs = moduleComponents.map { "${componentsClassName}.${it.moduleName}.${it.className.classNameToComponentName}" }.sorted()
        val components = stringDefs.joinToString(",\n") { "\t\t$it" }
        val newCompoent = stringDefFormatter.format(packageName, components, componentClassName)
        componentFile.writeBytes(newCompoent.toByteArray())
    }

    private fun String.toUnderlineCase(): String {
        val charArray = toCharArray()
        val buffer = StringBuffer()

        var i = 1
        val size = charArray.size
        val charA = 'A'
        val charZ = 'Z'
        val upper = charA..charZ
        buffer.append(charArray[0])
        while (i < size) {
            if (charArray[i] in upper) buffer.append("_")
            buffer.append(charArray[i++])
        }
        return buffer.toString()
    }
}

private class ModuleComponent {
    val moduleName: String
    val className: String


    constructor(mc: String) {
        val data = mc.split(":")
        this.moduleName = data.first()
        this.className = data.last()
    }

    constructor(moduleName: String, classeName: String) {
        this.moduleName = moduleName
        this.className = classeName
    }

    override fun toString(): String {
        return "//$moduleName:$className"
    }
}